// CyTargetLinker RIN conversion,
//
// scripts to create RIN network files for several online databases
// can be used with the CyTargetLinker app for Cytoscape
//
// Copyright 2011-2013 Department of Bioinformatics - BiGCaT, Maastricht University
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.bridgedb.DataSource;
import org.bridgedb.IDMapperStack;

import cytargetlinker.conversion.ArgsParser.AFilesAttributes;
import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.data.GeneNode;
import cytargetlinker.conversion.data.MTI;
import cytargetlinker.conversion.data.MiRNANode;
import cytargetlinker.conversion.graph.Graph;


/**
 * Converts miRNA target text files from Microcosm to a XGMML or GML network.
 * The input files for this script can be downloaded here:
 * 
 * http://www.ebi.ac.uk/enright-srv/microcosm/cgi-bin/targets/v5/download.pl
 * 
 * @author Thomas, Pooja, Naime, martina
 */
public class Microcosm {
	
	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	private static Args pargs;
	private interface Args extends AHelp, AFilesIn, AFilesOut, AFilesAttributes {}
	
	/**
	 * USAGE: java -cp conversion.jar cytargetlinker.conversion.Microcosm
	 * 
	 * REQUIRED ARGUMENTS:
	 * [-i] = input file path
	 * [-o] = output file path
	 * [-m] = list of bridgedb files for identifier mapping
	 * [-l] = log file path
	 * [--organism] = scientific name for the organism
	 * [-a] = annotation file (Transcript/Gene mapping - created by Biomart, separated by comma)
	 *  
	 * OPTIONAL ARGUMENTS:
	 * [-n] = network name
	 * 
	 */
	public static void main(String argv[]) throws Exception {
		pargs = ArgsParser.parse(argv, Args.class);
		if(pargs.getAnnotationFile() != null) {
			Microcosm converter = new Microcosm();
			converter.startConversion();
		} else {
			log.severe("Please add annotation file to map from Ensembl transcript to Ensembl gene id.");
		}
	}
	
	private IDMapperStack idMapper;
	private Graph graph;
	private Map<String, Integer> index;

	private Map<String, String> annotationMap;
	
	private Map<String, MiRNANode> miRNAs;
	private Map<String, GeneNode> genes;
	private Map<String, MTI> interactions;
	
	private List<String> nodesNotFound;
	private List<String> interactionIgnored;
	
	private List<String> noAnnotationMapping;
	
	public Microcosm() throws Exception {
		index = new HashMap<String, Integer>();
		annotationMap = new HashMap<String, String>();
		noAnnotationMapping = new ArrayList<String>();
		
		miRNAs = new HashMap<String, MiRNANode>();
		genes = new HashMap<String, GeneNode>();
		interactions = new HashMap<String, MTI>();
		nodesNotFound = new ArrayList<String>();
		interactionIgnored = new ArrayList<String>();
	}
	
	private void startConversion() throws Exception {
		File in = pargs.getInput();
		if(in != null) {
			Utils.setUpLogger(log, getLogFile(), false);
			idMapper = Utils.initIDMapper(pargs, false);
			if(idMapper == null) {
				log.severe("no bridgedb file specified");
			} else {
				log.info("conversion of microcosm file started ...\n");
	
				ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
					public Graph buildGraph(File in) throws Exception {
						return importMicrocosm(in);
					}
				});
					
				log.info("MicroCosm network file created.\n\n");
			}
		} else {
			System.out.println("Please specify input file!");
		}
	}
	
	protected Graph importMicrocosm(File in) throws IOException {
		graph = new Graph();
		
		readAnnotations();
		setNetworkAttributes(in);
		parseMicrocosm(in);
		
		log.info(interactions.size() + " interactions have been found.\n" + genes.size() + " gene nodes.\n" + miRNAs.size() + " miRNA nodes.\n");
		log.info(interactionIgnored.size() + " interactions were ignores because " + nodesNotFound.size() + " nodes could not be mapped");
		cleanUp();
		return graph;
	}
	
	private void parseMicrocosm(File in) {
		try {
			BufferedReader br = new BufferedReader(new FileReader(in));
			String[] header = readHeader(br);
			int missingInteractions = 0;
			if (header != null) {
				String line = null;

				List<String[]> rows = new ArrayList<String[]>();
				while ((line = br.readLine()) != null) {
					if (!line.startsWith("#") && !line.equals("")) {
						rows.add(line.split("\t", header.length));
					}
				}
				
				for (String[] row : rows) {
					String transcriptId = row[index.get("TRANSCRIPT_ID")];
					String geneName = row[index.get("EXTERNAL_NAME")];
					String mirna = row[index.get("SEQ")];
					String score = row[index.get("SCORE")];
					String pValue = row[index.get("PVALUE_OG")];

					if (annotationMap.containsKey(transcriptId)) {
						String geneNode = createGeneNode(annotationMap.get(transcriptId), geneName);
						String miRNANode = createMiRNANode(mirna);
						if(geneNode != null && miRNANode != null) {
							if(!interactions.containsKey(miRNANode + "_" + geneNode)) {
								MTI mti = new MTI(miRNAs.get(miRNANode), genes.get(geneNode), score, pValue, "");
								interactions.put(miRNANode + "_" + geneNode, mti);
							}
						} else {
							if(!interactionIgnored.contains(mirna + " -> " + geneName)) {
								interactionIgnored.add(mirna + " -> " + geneName);
							}
							missingInteractions++;
						}
					} else {
						if (!noAnnotationMapping.contains(transcriptId))
							noAnnotationMapping.add(transcriptId);
					}
				}
			}
			
			log.info("Create interactions finished (" + missingInteractions + " interactions were ignored.)");
			
			int count = 0;
			for(String str : interactions.keySet()) {
				MTI mti = interactions.get(str);
				mti.createEdge(graph, "MicroCosm version 5", count);
				count++;
			}
		} catch (IOException e) {
			log.warning("Could not read input file " + in.getAbsolutePath());
		}
	}
	
	private String[] readHeader(BufferedReader br) throws IOException {
		String[] header = null;
		String line = null;
		while((line = br.readLine()) != null) {
			if(line.startsWith("##GROUP")) {
				header = line.split("\t");
				index = new HashMap<String, Integer>();
				
				for(int i = 0; i < header.length; i++) index.put(header[i], i);
				break;
			}
		}
		return header;
	}
	
	private void setNetworkAttributes(File input) {
		graph.setTitle("MicroCosm v5 " + pargs.getOrganism());
		graph.setAttribute(CommonAttributes.DATABASE.getName(), "MicroCosm v5 (" + pargs.getOrganism() + ")");
	
		graph.setAttribute(CommonAttributes.TYPE.getName(), "predicted miRNA targets");
		graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), input.getName());
		graph.setAttribute(CommonAttributes.IDENTIFIERS.getName(), "identifiers");
		graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "Ensembl");
		graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "miRBase");
		graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "mirna");
		graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
	}

	private void readAnnotations() {
		File file = new File(pargs.getAnnotationFile());
		if(file.exists()) {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(file));
				String line;
				while((line = reader.readLine()) != null) {
					String [] buffer = line.split(",");
					if(!annotationMap.containsKey(buffer[1])) {
						annotationMap.put(buffer[1], buffer[0]);
					}
				}
				reader.close();
			} catch (IOException e) {
				log.severe("Could not read annotation file.\n");
				System.exit(0);
			}
		} else {
			log.severe("Annotation file does not exist.\n");
			System.exit(0);
		}
	}

	private String createGeneNode(String geneId, String geneName) {
		
		GeneNode node = GeneNode.createGeneNode(geneId, geneName, idMapper, DataSource.getBySystemCode("En"));
		if(node != null) {
			if(!genes.containsKey(node.getId())) {
				genes.put(node.getId(), node);
			}
			return node.getId();
		} else {
			if(!nodesNotFound.contains(geneId)) {
				nodesNotFound.add(geneId);
			}
			return null;
		}	
	}

	private String createMiRNANode(String miRNA) {
		MiRNANode node = MiRNANode.createMiRNANode(miRNA, idMapper, DataSource.getBySystemCode("Mb"));
		if(node != null) {
			if(!miRNAs.containsKey(node.getId())) {
				miRNAs.put(node.getId(), node);
			}
			return node.getId();
		} else {
			if(!nodesNotFound.contains(miRNA)) {
				nodesNotFound.add(miRNA);
			}
			return null;
		}
	}
	
	private File getLogFile() {
		if(pargs.isLogFile()) {
			return pargs.getLogFile();
		} else {
			if(pargs.isInput()) {
				return new File(pargs.getInput() + ".log");
			} else {
				return null;
			}
		}
	}
	
	private void cleanUp() {
		genes.clear();
		miRNAs.clear();
		interactions.clear();
		nodesNotFound.clear();
		interactionIgnored.clear();
	}
}
